﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VsxFactory.Modeling.Repository;
using System.Linq.Expressions;
using System.Diagnostics;
using System.Reflection;

namespace VsxFactory.Modeling.Strategies
{
    public static class PackageExtensions
    {
        private static readonly string[] _packagePropertiesToSearch = new[] { "ID", "Description" };

        public static PackageInfo FindByVersion(this IEnumerable<PackageInfo> source, Version minVersion, Version maxVersion, Version exactVersion)
        {
            IEnumerable<PackageInfo> packages = from p in source
                                             orderby p.Version descending
                                             select p;

            if (exactVersion != null)
            {
                // Try to match the exact version
                packages = packages.Where(p => Version.Parse(p.Version) == exactVersion);
            }
            else
            {
                if (minVersion != null)
                {
                    // Try to match the latest that satisfies the min version if any
                    packages = packages.Where(p => Version.Parse(p.Version) >= minVersion);
                }

                if (maxVersion != null)
                {
                    // Try to match the latest that satisfies the max version if any
                    packages = packages.Where(p => Version.Parse(p.Version) <= maxVersion);
                }
            }

            return packages.FirstOrDefault();
        }

        public static IQueryable<PackageInfo> Find(this IQueryable<PackageInfo> packages, params string[] searchTerms)
        {
            if (searchTerms == null)
            {
                return packages;
            }

            IEnumerable<string> nonNullTerms = searchTerms.Where(s => s != null);
            if (!nonNullTerms.Any())
            {
                return packages;
            }

            return packages.Where(BuildSearchExpression(nonNullTerms));
        }

        /// <summary>
        /// Constructs an expression to search for individual tokens in a search term in the Id and Description of packages
        /// </summary>
        private static Expression<Func<PackageInfo, bool>> BuildSearchExpression(IEnumerable<string> searchTerms)
        {
            Debug.Assert(searchTerms != null);
            var parameterExpression = Expression.Parameter(typeof(PackageInfo));
            // package.Id.ToLower().Contains(term1) || package.Id.ToLower().Contains(term2)  ...
            Expression condition = (from term in searchTerms
                                    from property in _packagePropertiesToSearch
                                    select BuildExpressionForTerm(parameterExpression, term, property)).Aggregate(Expression.OrElse);
            return Expression.Lambda<Func<PackageInfo, bool>>(condition, parameterExpression);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.String.ToLower",
            Justification = "The expression is remoted using Odata which does not support the culture parameter")]
        private static Expression BuildExpressionForTerm(ParameterExpression packageParameterExpression, string term, string propertyName)
        {
            MethodInfo stringContains = typeof(String).GetMethod("Contains");
            MethodInfo stringToLower = typeof(String).GetMethod("ToLower", Type.EmptyTypes);

            // package.Id / package.Description
            var propertyExpression = Expression.Property(packageParameterExpression, propertyName);
            // .ToLower()
            var toLowerExpression = Expression.Call(propertyExpression, stringToLower);
            // .Contains(term.ToLower())
            return Expression.Call(toLowerExpression, stringContains, Expression.Constant(term.ToLower()));
        }
    }

}
